package org.swift.mashup.engine.concurrency.execution;

import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.swift.mashup.engine.concurrency.memory.MemoryType;

/**
 * 
 * @author hshober The <b>RuleSet</b> class provides the container for a set of
 *         rule definitions.<br>
 *         As rules are added to the RuleSet using <i>addRule()</i> the data
 *         types used by the rule as well as the return type of the rule are
 *         added to <b>RulePatterns</b>. These patterns are constructed, one per
 *         unique data type combination and are assigned a bit mask.<br>
 *         This class contains methods that are used at runtime when evaluating
 *         the rules. All of the runtime state is passed in as part of the
 *         working memory. One <b>RuleSet</b> instance can be used by many
 *         reasoners across threads. Definition state is the only state
 *         maintained.
 * 
 */
public class RuleSet {
	protected Map<String, Object> defaultValues = new HashMap<String, Object>();
	
	private Map<String, MemoryType> memoryTypes = new HashMap<String, MemoryType>();
	private Map<BitSet, RulePattern> patterns = new HashMap<BitSet, RulePattern>();
	private Map<String, Rule> rules = new HashMap<String, Rule>();
	private Map<String, Rule> unmatchedRules = new HashMap<String, Rule>();
	private volatile int typeCount = 0;
	private volatile int patternCount = 0;
	
	public RuleSet clone(boolean includeRules){
		RuleSet ruleSet = new RuleSet();
		if (includeRules){
			for (Rule rule : rules.values()){
				ruleSet.addRule(rule);
			}
		}
		for (String valueKeys : defaultValues.keySet()){
			ruleSet.addMemoryType(valueKeys);
		}
		ruleSet.defaultValues.putAll(this.defaultValues);
		return ruleSet;
	}

	/**
	 * Getter of the property <tt>rule</tt>
	 * 
	 * @return Returns the rule.
	 * 
	 */
	public Map<String, Rule> getRules() {
		return rules;
	}

	/**
	 * Returns an iterator over the elements in this collection.
	 * 
	 * @return an <tt>Iterator</tt> over the elements in this collection
	 * @see java.util.Collection#iterator()
	 * 
	 */
	public Iterator<Rule> ruleIterator() {
		return rules.values().iterator();
	}

	/**
	 * Returns <tt>true</tt> if this collection contains no elements.
	 * 
	 * @return <tt>true</tt> if this collection contains no elements
	 * @see java.util.Collection#isEmpty()
	 * 
	 */
	public boolean isRulesEmpty() {
		return rules.isEmpty();
	}

	/**
	 * Returns <tt>true</tt> if this collection contains the specified element.
	 * 
	 * @param element
	 *            whose presence in this collection is to be tested.
	 * @see java.util.Collection#contains(Object)
	 * 
	 */
	public boolean containsRule(Rule Rule) {
		return rules.containsKey(Rule.getName());
	}

	/**
	 * Returns the number of elements in this collection.
	 * 
	 * @return the number of elements in this collection
	 * @see java.util.Collection#size()
	 * 
	 */
	public int rulesSize() {
		return rules.size();
	}

	/**
	 * Returns all elements of this collection in an array.
	 * 
	 * @return an array containing all of the elements in this collection
	 * @see java.util.Collection#toArray()
	 * 
	 */
	public Rule[] rulesToArray() {
		return rules.values().toArray(new Rule[rules.size()]);
	}

	/**
	 * <i>addRule</i> places new rules into the <b>RuleSet</b> and calls the
	 * methods that identify and categorize rules based on their pattern of
	 * parameter data types.
	 * 
	 * @param element
	 *            whose presence in this collection is to be ensured.
	 * @see java.util.Collection#add(Object)
	 * 
	 */
	public boolean addRule(Rule rule) {
		boolean result = false;
		String ruleName = rule.getName();
		if (rules.containsValue(rule))
			return true;
		if (!this.getMemoryTypes().containsKey(ruleName)) {
			MemoryType type = addMemoryType(ruleName);
			type.setPublish(rule.isPublishResults());
		}
		try {
			addRuleToPatterns(rule);
			if (unmatchedRules.containsKey(ruleName)) {
				unmatchedRules.remove(ruleName);
			}
			rules.put(ruleName, rule);
			result = true;
			/*
			 * Recursively add rules that have previously been omitted
			 */
			Object[] rulesLeftUnmatched = unmatchedRules.values().toArray();
			for (Object currRule : rulesLeftUnmatched) {
				addRule((Rule) currRule);
			}
		} catch (RuleNotFoundException e) {
			unmatchedRules.put(ruleName, rule);
		}
		return result;
	}

	private RulePattern addRuleToPatterns(Rule rule)
			throws RuleNotFoundException {
		RulePattern pattern = null;
		Collection<MemoryType> types = new ArrayList<MemoryType>();
		for (Parameter param : rule.getParameters().values()) {
			types.addAll(parseTypesFromValueString(param.getValue()));
		}
		BitSet ruleMask = computeTypeMask(types);
		if (patterns.containsKey(ruleMask)) {
			pattern = patterns.get(ruleMask);
		} else {
			pattern = new RulePattern(++patternCount, types);
			patterns.put(pattern.getMask(), pattern);
		}
		pattern.addRule(rule);
		rule.setPattern(pattern);
		return pattern;
	}

	private BitSet computeTypeMask(Collection<MemoryType> types) {
		BitSet mask = new BitSet();
		for (MemoryType type : types) {
			mask.or(type.getMask());
		}
		return mask;
	}

	private Collection<MemoryType> parseTypesFromValueString(String value)
			throws RuleNotFoundException {
		Collection<MemoryType> result = new ArrayList<MemoryType>();
		String typeName = value;
		if (typeName.trim().toLowerCase().startsWith("collect(")) {
			typeName = typeName.trim().replace("collect(", "").replace(")", "");
			if (!memoryTypes.containsKey(typeName)) {
				throw new RuleNotFoundException("", typeName);
			}
			if (!memoryTypes.containsKey(value)) {
				addMemoryType(value);
			}

		} else if (!memoryTypes.containsKey(typeName)) {
			throw new RuleNotFoundException("", typeName);
		}
		result.add(memoryTypes.get(value));
		return result;
	}

	/**
	 * Removes all of the elements from this collection (optional operation).
	 * 
	 * @see java.util.Collection#clear()
	 * 
	 */
	public void clearRules() {
		// TODO add code to reset all of the collections
		rules.clear();
	}

	public Map<String, MemoryType> getMemoryTypes() {
		return memoryTypes;
	}

	/**
	 * <i>addMemoryType</i> is used to add one data type declaration for each
	 * unique data type used within the <b>RuleSet</b>.
	 * 
	 * @param name
	 * @return
	 */
	public MemoryType addMemoryType(String name) {
		MemoryType type = new MemoryType(++typeCount, name);
		if (name.toLowerCase().startsWith("collect(")) {
			MemoryType collectType = this.getMemoryTypes().get(
					type.getCollectedTypeName());
			if (collectType != null)
				collectType.setCollect(true);
		}
		this.getMemoryTypes().put(type.getName(), type);
		return type;
	}

	public List<RulePattern> matchPatterns(Collection<MemoryType> types) {
		BitSet mask = computeTypeMask(types);
		return matchPatterns(mask);
	}

	public List<RulePattern> matchPatterns(MemoryType type) {
		BitSet mask = type.getMask();
		return matchPatterns(mask);
	}

	public List<RulePattern> matchPatterns(BitSet mask) {
		List<RulePattern> result = new ArrayList<RulePattern>();
		for (RulePattern pattern : this.patterns.values()) {
			BitSet patMask = pattern.getMask();
			if (patMask.intersects(mask))
				result.add(pattern);
		}
		return result;
	}

	public List<Rule> matchRules(MemoryType type, BitSet memoryMask) {
		BitSet mask = type.getMask();
		return matchRules(mask, memoryMask);
	}

	public List<Rule> matchRules(BitSet primaryMask, BitSet secondaryMask) {
		List<Rule> result = new ArrayList<Rule>();
		for (RulePattern pattern : this.patterns.values()) {
			BitSet patMask = new BitSet();
			patMask.or(pattern.getMask());
			if (primaryMask.intersects(patMask)) {
				patMask.and(secondaryMask);
				if (pattern.getMask().equals(patMask)) {
					result.addAll(pattern.getRules());
				}
			}
		}
		return result;
	}

	public List<RulePattern> matchPatterns(BitSet primaryMask,
			BitSet secondaryMask) {
		List<RulePattern> result = new ArrayList<RulePattern>();
		for (RulePattern pattern : this.patterns.values()) {
			BitSet patMask = new BitSet();
			patMask.or(pattern.getMask());
			if (primaryMask.intersects(patMask)) {
				patMask.and(secondaryMask);
				if (pattern.getMask().equals(patMask)) {
					result.add(pattern);
				}
			} else if (primaryMask.equals(new BitSet())
					&& patMask.equals(new BitSet())) {
				result.add(pattern);
			}
		}
		return result;
	}

	private class RuleNotFoundException extends Exception {
		private static final long serialVersionUID = 1L;

		RuleNotFoundException(String message, String ruleName) {
			super("Error: rule [" + ruleName + "] not found." + message);
		}
	}

	public Map<String, Object> getDefaultValues() {
		return defaultValues;
	}

	public void setDefaultValues(Map<String, Object> defaultValues) {
		this.defaultValues = defaultValues;
	}
}
